//
//  RxAlamofire.swift
//  SwiftCode
//
//  Created by yuhus on 2023/8/10.
//

import RxSwift
import Alamofire

/// Default instance of unknown error
public let RxAlamofireUnknownError = NSError(domain: "RxAlamofireDomain", code: -1, userInfo: nil)

extension Alamofire.Session: ReactiveCompatible {}

protocol RxAlamofireRequest {
  func responseWith(completionHandler: @escaping (RxAlamofireResponse) -> Void)
  func resume() -> Self
  func cancel() -> Self
}

protocol RxAlamofireResponse {
  var error: Error? { get }
}

extension DataResponse: RxAlamofireResponse {
  var error: Error? {
    switch result {
    case let .failure(error):
      return error
    default:
      return nil
    }
  }
}

extension DownloadResponse: RxAlamofireResponse {
  var error: Error? {
    switch result {
    case let .failure(error):
      return error
    default:
      return nil
    }
  }
}

extension DataRequest: RxAlamofireRequest {
  func responseWith(completionHandler: @escaping (RxAlamofireResponse) -> Void) {
    response { response in
      completionHandler(response)
    }
  }
}

extension DownloadRequest: RxAlamofireRequest {
  func responseWith(completionHandler: @escaping (RxAlamofireResponse) -> Void) {
    response { response in
      completionHandler(response)
    }
  }
}

public extension Reactive where Base: Alamofire.Session {
  internal func request<R: RxAlamofireRequest>(_ createRequest: @escaping (Alamofire.Session) throws -> R) -> Observable<R> {
    return Observable.create { observer -> Disposable in
      let request: R
      do {
        request = try createRequest(self.base)
        observer.on(.next(request))
        request.responseWith { response in
          if let error = response.error {
            observer.on(.error(error))
          } else {
            observer.on(.completed)
          }
        }
        
        if !self.base.startRequestsImmediately {
          _ = request.resume()
        }
        
        return Disposables.create {
          _ = request.cancel()
        }
      } catch {
        observer.on(.error(error))
        return Disposables.create()
      }
    }
  }
  
  func request(_ method: HTTPMethod, _ url: URLConvertible) -> Observable<DataRequest> {
    return request { session in
      session.request(url, method: method)
    }
  }
}

// MARK: Request - Validation

public extension ObservableType where Element == DataRequest {
  func validate<S: Sequence>(statusCode: S) -> Observable<Element> where S.Element == Int {
    return map { $0.validate(statusCode: statusCode) }
  }
  
  func validate() -> Observable<Element> {
    return map { $0.validate() }
  }
  
  func validate<S: Sequence>(contentType acceptableContentTypes: S) -> Observable<Element> where S.Iterator.Element == String {
    return map { $0.validate(contentType: acceptableContentTypes) }
  }
  
  func validate(_ validation: @escaping DataRequest.Validation) -> Observable<Element> {
    return map { $0.validate(validation) }
  }
}

extension Request: ReactiveCompatible {}
